package model;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import org.apache.log4j.Logger;

public class Center {

	private final Logger log;

	private final Queue<Request> queue = new LinkedList<Request>();

	private final Map<Request, Double> serviceBegining;

	private Map<Request, RequestFinishEvent> processedRequestsFinishEvent = new HashMap<Request, RequestFinishEvent>();

	private final int channels;

	private final String name;

	public Center(String name, int channels) {
		this.channels = channels;
		this.name = name != null ? name : "center";
		serviceBegining = new HashMap<Request, Double>(channels);
		log = Logger.getLogger(this.name);
	}

	public void queue(final Request r) {
		log.debug("queueing request " + r);
		queue.add(r);
		serviceBegining.put(r, Time.currentTime());
		pool();
	};

	private boolean canServeNow() {
		return !queue.isEmpty() && processedRequestsFinishEvent.size() < channels;
	}

	protected void pool() {
		if (canServeNow()) {
			final Request r = queue.poll();

			RequestFinishEvent e = new RequestFinishEvent(r, 1.0, Time.currentTime()
					+ r.getCost());

			processedRequestsFinishEvent.put(r, e);
			Time.registerEvent(e);
		}
		fixCurrentServiceTime();
	}

	private void fixCurrentServiceTime() {
		final double factor = computeCurrentInterferenceFactor();

		for (Request r : processedRequestsFinishEvent.keySet()) {
			RequestFinishEvent oldEvent = processedRequestsFinishEvent.get(r);

			if (oldEvent.getCurrentFactor() != factor) {
				final double timeLeft = (oldEvent.getTime() - Time.currentTime())
						* (factor / oldEvent.getCurrentFactor());

				log.debug("correcting time for event " + oldEvent + ": "
						+ (oldEvent.getTime() - Time.currentTime()) + " -> " + timeLeft);

				Time.cancelEvent(oldEvent);

				RequestFinishEvent e = new RequestFinishEvent(r, factor,
						Time.currentTime() + timeLeft);

				processedRequestsFinishEvent.put(r, e);

				Time.registerEvent(e);
			}
		}
	}

	private double computeCurrentInterferenceFactor() {
		return Math.sqrt(processedRequestsFinishEvent.size());
	}

	private class RequestFinishEvent extends Event {

		private final Request r;

		private final double currentFactor;

		public RequestFinishEvent(final Request r, double currentFactor,
				double timeLeft) {
			super(timeLeft);
			this.r = r;
			this.currentFactor = currentFactor;
		}

		public void perform() {
			final double ct = Time.currentTime();
			Double serviceTime = ct - serviceBegining.remove(r);
			log.info("finished " + r + " at " + ct + " (took " + serviceTime + ")");
			processedRequestsFinishEvent.remove(r);
			pool();
		}

		public double getCurrentFactor() {
			return currentFactor;
		}

		@Override
		public String getName() {
			return "RequestFinishEvent";
		}
	}
}
